package com.gmlive.plugin.jenkins

import com.android.build.gradle.api.ApplicationVariant
import com.android.build.gradle.api.BaseVariant
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
import com.android.builder.model.ProductFlavor
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import java.io.File
import java.text.SimpleDateFormat
import java.util.*

/**
 * 从属性中获取：
 * 1、buildName          编译的项目名称
 * 2、buildId            编译的任务id
 * 3、buildBranch        编译的分支
 * 4、buildUser          编译执行人
 * 5、buildOutput        编译输出路径
 * 6、downloadService    下载服务根路径
 * 7、buildChannel       编译的渠道
 */
@Suppress("DefaultLocale")
open class JenkinsTask : DefaultTask() {
    @Internal
    var configuration: JenkinsExtension

    @Internal
    var android: BaseAppModuleExtension

    @Internal
    var buildOptions = mutableListOf<JenkinsOptions>()

    companion object {
        // const val REINFORCE_AIJIAMI_SUFFIX_APK = "aijiami.apk"
        const val REINFORCE_DEXGUARD_SUFFIX_APK = "protected.apk"
        const val REINFORCE_DEXGUARD_SUFFIX_AAB = "protected.aab"
        const val REINFORCE_APPGUARD_SUFFIX_APK = "guard.apk"
        const val REINFORCE_APPGUARD_SUFFIX_AAB = "guard.aab"
        const val AAB_RESGUARD_SUFFIX_AAB = "resguard.aab"
    }

    init {
        description = "Jenkins build task"
        group = "jenkins"

        android = project.extensions.findByName("android") as BaseAppModuleExtension
        configuration = project.extensions.findByName("jenkinsConfig") as JenkinsExtension

        println("[Jenkins] 配置Jenkins打包任务")
        val buildOutput = JenkinsUtils.requireProperty(project, "buildOutput")
        val buildName = JenkinsUtils.requireProperty(project, "buildName")
        val buildId = JenkinsUtils.requireProperty(project, "buildId")
        val buildUrl = JenkinsUtils.requireProperty(project, "buildUrl")
        val buildUser = JenkinsUtils.requireProperty(project, "buildUser")
        val buildBranch = JenkinsUtils.requireProperty(project, "buildBranch")
        val downloadService = JenkinsUtils.requireProperty(project, "downloadService")
        val buildChannel = JenkinsUtils.requireProperty(project, "buildChannel")
        val atMobilesStr = JenkinsUtils.requireProperty(project, "atMobiles")
        val buildDesc = JenkinsUtils.requireProperty(project, "buildDesc")
        val uploadFir =
            project.properties.getOrDefault("uploadFir", "false").toString().equals("true", true)

        val needGuard = JenkinsUtils.requireProperty(project, "useReinforce").equals("true", true)
        val useBundle = JenkinsUtils.requireProperty(project, "useBundle").equals("true", true)
        val isRelease = JenkinsUtils.requireProperty(project, "isRelease").equals("true", true)

        val hasResGuard = project.plugins.hasPlugin("AndResGuard")
        val hasDexGuard = project.plugins.hasPlugin("dexguard")
        val hasAppGuard = project.plugins.hasPlugin("appguard")
        val hasObscureGuard = project.plugins.hasPlugin("obscure-plugin")
        val hasArcherGuard = project.plugins.hasPlugin("com.gmlive.archer")

        if (needGuard && useBundle && !hasAppGuard) {
            throw GradleException("aab加固需要配置 appguard 插件")
        }

        //只有加固才会有源apk
        val keepOriginalApk = JenkinsUtils.requireProperty(project, "keepOriginalApk")
            .equals("true", false) && (needGuard || hasDexGuard)
        println("[Jenkins] keepOriginalApk:$keepOriginalApk")

        android.applicationVariants
            //只有与本Task相匹配的variant，才能进行如下任务
            .matching { isTargetVariant(it) }
            .all { variant ->
                val useV2Signing = variant.signingConfig.isV2SigningEnabled
                var originApkFile = ""

                variant.outputs.forEach { output ->
                    // APK路径
                    println("[Jenkins] ${variant.name} - ${output.outputFile.absolutePath}")
                    val targetApkFile = findTargetApk(
                        output.outputFile,
                        needGuard,
                        hasResGuard,
                        hasDexGuard,
                        useV2Signing
                    )

                    val targetAABFile =
                        if (useBundle) getOutputBundlePath(variant, needGuard) else ""
                    // mapping路径
                    val mappingFile = findTargetMapping(project, hasDexGuard, variant)

                    // APM method mapping文件
                    val apmMethodMappingFile = getAPMMethodMappingPath(project, variant.name)

                    //马甲包防关联插件的mapping
                    val obscureMappingFile = findTargetObscureMapping(
                        project,
                        variant.name,
                        hasObscureGuard,
                        hasArcherGuard
                    )

                    //andResGuard插件的res mapping
                    val resGuardResMapping =
                        findTargetResGuardResMapping(output.outputFile, hasResGuard)

                    val applicationId = variant.applicationId
                    val appVersionName = variant.versionName
                    val appVersionCode = variant.versionCode
                    val hotfixConfigFile = "${project.projectDir}/robust/config.hotfix"

                    if (keepOriginalApk) {
                        originApkFile = if (!useBundle) {
                            findTargetApk(
                                output.outputFile,
                                false,
                                hasResGuard,
                                false,
                                useV2Signing
                            )
                        } else {
                            getBundleFilePath(project, variant).absolutePath
                        }
                    }

                    buildOptions.add(
                        JenkinsOptions(
                            buildName = buildName,
                            buildId = buildId,
                            buildBranch = buildBranch,
                            buildUser = buildUser,
                            buildOutput = buildOutput,
                            buildUrl = buildUrl,
                            downloadServiceUrl = downloadService,
                            buildChannel = buildChannel,
                            srcApkFile = targetApkFile,
                            srcMappingFile = mappingFile,
                            apmMethodMappingFile = apmMethodMappingFile,
                            obscureMappingFile = obscureMappingFile,
                            resGuardResMappingFile = resGuardResMapping,
                            packageName = applicationId,
                            versionName = appVersionName,
                            versionCode = appVersionCode,
                            uploadFir = uploadFir,
                            atMobilesStr = atMobilesStr,
                            buildDesc = buildDesc,
                            useReinforce = needGuard || hasDexGuard,
                            hotfixConfigFile = hotfixConfigFile,
                            useBundle = useBundle,
                            srcAABFile = targetAABFile,
                            isRelease = isRelease,
                            originApkFile = originApkFile,
                            signConfig = variant.signingConfig
                        )
                    )
                }
            }

    }

    @TaskAction
    fun run() {
        project.gradle.buildFinished {
            println("[Jenkins] buildFinished")
            jenkinsAction()
        }
    }

    @Suppress("SimpleDateFormat")
    private fun jenkinsAction() {
        println("[Jenkins] buildOptions = $buildOptions")
        println("[Jenkins] jenkinsConfig = $configuration")

        //当前时间
        val buildDate = SimpleDateFormat("yyyyMMdd").format(Date())
        println("[Jenkins] buildDate = $buildDate")

        buildOptions.forEach { option ->
            // 处理拷贝输出路径
            val middlePath = option.buildName.plus(File.separator)
                .plus(
                    // 自定义渠道名
                    if (option.buildChannel.isNotEmpty()) {
                        option.buildChannel.plus(File.separator)
                    } else {
                        ""
                    }
                )
                .plus(
                    // 区分是否发布
                    if (option.isRelease) {
                        "publish"
                    } else {
                        "develop"
                    }
                )
                .plus(File.separator)
                .plus(option.versionName)
                .plus(File.separator)
                .plus(buildDate)
                .plus(File.separator)
                .plus(option.buildId)
            // 拷贝编译文件到目标目录
            val destDir = "${option.buildOutput}/$middlePath"
            project.copy {
                if (option.srcApkFile.isNotBlank()) {
                    it.from(option.srcApkFile)
                }
                // 避免没有mapping崩溃
                if (option.srcMappingFile.isNotEmpty()) {
                    it.from(option.srcMappingFile)
                }

                // 复制APM apm method mapping文件
                option.apmMethodMappingFile?.let { methodMapping ->
                    if (methodMapping.isNotBlank()) {
                        it.from(methodMapping)
                    }
                }

                //复制马甲包插件mapping
                option.obscureMappingFile?.let { mapping ->
                    if (mapping.isNotBlank()) {
                        it.from(mapping)
                    }
                }

                //复制andResGuard res mapping
                option.resGuardResMappingFile?.let { mapping ->
                    if (mapping.isNotBlank()) {
                        it.from(mapping)
                    }
                }

                if (option.hotfixConfigFile.isNotBlank()) {
                    it.from(option.hotfixConfigFile)
                }
                if (option.originApkFile.isNotBlank()) {
                    it.from(option.originApkFile)
                }
                it.into(destDir)
            }
            if (option.srcAABFile.isNotBlank()) {
                println("[Jenkins] extract apk, input file -> ${option.srcAABFile}")
                val apksFileName = File(option.srcAABFile).name.replace(".aab", ".apks")
                val success = JenkinsUtils.extractApks(project, option, apksFileName)
                project.copy {
                    it.from(option.srcAABFile)
                    if (success) {
                        it.from(apksFileName)
                    }
                    it.into(destDir)
                    //替换原来bundle编译出来的aab名字，加上了versionName
                    it.rename { fileName ->
                        val replace = option.versionName
                        fileName.replace("app", replace)
                    }
                }
            }

            //找到patch文件，并拷贝到目标目录
            project.fileTree("${project.buildDir}/outputs/robust").forEach { item ->
                if (item.name.startsWith("patch") && item.name.endsWith(".so")) {
                    project.copy {
                        it.from(item.path)
                        it.into(destDir)
                    }
                }
            }

            val outputRootPath = "${option.downloadServiceUrl}/$middlePath"
            var outputApkUrl = ""
            var outputOriginApkUrl = ""
            var outputAABUrl = ""
            var outputMappingUrl = ""
            var outputApmMethodMappingUrl = ""
            var outputObscureMappingUrl = ""
            var outputResGuardResMappingUrl = ""
            var outputApkSize = ""
            var outputApkMd5 = ""
            var outputHotfixConfigUrl = ""
            var outputPatchUrl = ""
            var outputExtractApks = ""

            project.fileTree(destDir).forEach { item ->
                if (isOutputApk(option, item)) {
                    outputApkUrl = "$outputRootPath/${item.name}"
                    outputApkMd5 = JenkinsUtils.md5Sum(item)
                    outputApkSize = JenkinsUtils.fileSize(item)
                }

                //只有加固包才有
                if (isOriginOutput(option, item)) {
                    outputOriginApkUrl = "$outputRootPath/${item.name}"
                }

                if (isOutputBundle(option, item)) {
                    outputAABUrl = "$outputRootPath/${item.name}"
                    outputApkMd5 = JenkinsUtils.md5Sum(item)
                    outputApkSize = JenkinsUtils.fileSize(item)
                }

                // Extract apks from app bundle
                if (item.name.endsWith("apks")) {
                    outputExtractApks = "$outputRootPath/${item.name}"
                }

                if (item.name.endsWith(".txt")) {
                    when {
                        item.name.endsWith("methodMapping.txt") -> {
                            outputApmMethodMappingUrl = "$outputRootPath/${item.name}"
                        }
                        item.name.endsWith("obscureMapping.txt") -> {
                            outputObscureMappingUrl = "$outputRootPath/${item.name}"
                        }
                        item.name.startsWith("resource_mapping_") -> {
                            outputResGuardResMappingUrl = "$outputRootPath/${item.name}"
                        }
                        else -> {
                            outputMappingUrl = "$outputRootPath/${item.name}"
                        }
                    }
                }

                if (item.name.endsWith("hotfix")) {
                    outputHotfixConfigUrl = "$outputRootPath/${item.name}"
                }

                if (item.name.startsWith("patch") && item.name.endsWith(".so")) {
                    outputPatchUrl = "$outputRootPath/${item.name}"
                }
            }

            println("[Jenkins] apkUrl = $outputApkUrl")
            println("[Jenkins] originApkUrl = $outputOriginApkUrl")
            println("[Jenkins] aabUrl = $outputAABUrl")
            println("[Jenkins] apkSize = $outputApkSize, apkMd5 = $outputApkMd5")
            println("[Jenkins] mappingUrl = $outputMappingUrl")
            println("[Jenkins] apmMethodMappingUrl = $outputApmMethodMappingUrl")
            println("[Jenkins] outputObscureMappingUrl = $outputObscureMappingUrl")
            println("[Jenkins] outputResGuardResMappingUrl = $outputResGuardResMappingUrl")
            println("[Jenkins] hotfixConfigUrl = $outputHotfixConfigUrl")
            println("[Jenkins] outputPatchUrl = $outputPatchUrl")

            // 是否上传fir
            if (option.uploadFir) {
                FirUtils.uploadFir(configuration, option)
            }

            // 发送apm信息
            val apkUrl = outputOriginApkUrl.ifBlank { outputApkUrl }
            if (apkUrl.isNotBlank()) {
                ApmServiceUtils.sendBuildApkMsg(apkUrl)
            }

            // 发送钉钉消息
            DingTalkUtils.sendDingBuildApkMsg(
                apkUrl = outputApkUrl,
                originApkUrl = outputOriginApkUrl,
                aabUrl = outputAABUrl,
                extractApks = outputExtractApks,
                mappingUrl = outputMappingUrl,
                apmMethodMappingUrl = outputApmMethodMappingUrl,
                obscureMappingUrl = outputObscureMappingUrl,
                resGuardResMappingUrl = outputResGuardResMappingUrl,
                apkMD5 = outputApkMd5,
                apkSize = outputApkSize,
                hotfixConfigUrl = outputHotfixConfigUrl,
                patchUrl = outputPatchUrl,
                jenkinsOptions = option,
                jenkinsConfig = configuration
            )
        }
    }

    /**
     * 寻找输出的apk。
     * 当有originApk信息时，则需要匹配爱加密、dexguard特殊后缀的apk
     * 当无originApk信息时，只需找apk结尾的即可
     */
    private fun isOutputApk(option: JenkinsOptions, itemFile: File): Boolean {
        val srcFileName = option.srcApkFile.split(File.separator).last()
        println("[Jenkins] Target output file name : $srcFileName")
        return itemFile.name.equals(srcFileName)
    }

    /**
     * 是否为最终输出的aab
     */
    private fun isOutputBundle(options: JenkinsOptions, bundleFile: File): Boolean {
        return (options.originApkFile.isNotBlank() &&
                bundleFile.name.endsWith(REINFORCE_APPGUARD_SUFFIX_AAB))
                || (options.originApkFile.isBlank() &&
                bundleFile.name.endsWith("aab"))
    }

    /**
     * 寻找输出的未加固apk，只有使用了爱加密、dexguard才会有为加固apk
     * 此apk信息需要满足：有originApk信息、以apk结尾、不能匹配爱加密、dexguard特殊后缀
     */
    private fun isOutputOriginApk(option: JenkinsOptions, itemFile: File): Boolean {
        return option.originApkFile.isNotBlank()
                && itemFile.name.endsWith("apk")
                && !itemFile.name.endsWith(REINFORCE_APPGUARD_SUFFIX_APK)
                && !itemFile.name.endsWith(REINFORCE_DEXGUARD_SUFFIX_APK)
    }

    private fun isOriginOutput(option: JenkinsOptions, item: File): Boolean {
        return if (option.originApkFile.isNotBlank()) {
            if (option.useBundle) {
                (item.name.endsWith("aab")
                        && !item.name.endsWith(REINFORCE_APPGUARD_SUFFIX_AAB))
            } else {
                (item.name.endsWith("apk")
                        && !item.name.endsWith(REINFORCE_APPGUARD_SUFFIX_APK)
                        && !item.name.endsWith(REINFORCE_DEXGUARD_SUFFIX_APK))
            }
        } else {
            false
        }
    }

    /**
     * 是否是有效的variant。
     * 通过[com.android.builder.model.ProductFlavor]与[com.android.builder.model.BuildType]与
     * 本Task任务的名称[org.gradle.api.DefaultTask.getName]相匹配
     */
    private fun isTargetVariant(variant: ApplicationVariant): Boolean {
        val variantName = this.name.substring(("jenkins".length), this.name.length)
        return variantName.equals(variant.buildType.name, true)
                || isTargetFlavor(variantName, variant.productFlavors, variant.buildType.name)
    }

    private fun isTargetFlavor(
        variantName: String,
        flavors: List<ProductFlavor>,
        buildType: String
    ): Boolean {
        if (flavors.isNotEmpty()) {
            val flavor = flavors[0].name
            val variant = flavors.joinToString(separator = "", transform = {
                it.name
            }).plus(buildType)
            return variantName.equals(flavor, true) ||
                    variantName.equals(variant, true)
        }
        return false
    }

    // 查找目标apk
    private fun findTargetApk(
        outputFile: File,
        needGuard: Boolean,
        hasResGuard: Boolean,
        hasDexGuard: Boolean,
        useV2Signing: Boolean
    ): String {
        // remove apk
        val fileName = outputFile.name.substring(0, outputFile.name.length - 4)

        return when {
            needGuard -> {
                "${outputFile.parent}/${fileName}_$REINFORCE_APPGUARD_SUFFIX_APK"
            }
            hasResGuard -> {
                if (useV2Signing) {
                    "${outputFile.parent}/AndResGuard_${fileName}/${fileName}_aligned_signed.apk"
                } else {
                    "${outputFile.parent}/AndResGuard_${fileName}/${fileName}_signed_aligned.apk"
                }
            }
            hasDexGuard -> {
                "${outputFile.parent}/${fileName}-$REINFORCE_DEXGUARD_SUFFIX_APK"
            }
            else -> {
                outputFile.absolutePath
            }
        }
    }

//    // 查找目标apk
//    private fun findTargetAAB(variant: BaseVariant, hasDexGuard: Boolean): String {
//        val path = "${project.buildDir}/outputs/bundle/${variant.name}"
//        val matchingStr = if (hasDexGuard) "*-$REINFORCE_DEXGUARD_SUFFIX_AAB" else "*.aab"
//        return project.fileTree(path)
//            .matching { it.include(matchingStr) }
//            .firstOrNull()
//            ?.absolutePath ?: ""
//    }

    private fun getOutputBundlePath(variant: BaseVariant, useGuard: Boolean): String {
        println("[Jenkins] getOutputBundlePath, variant -> ${variant.name}, useGuard -> $useGuard")
        val bundleTaskOutputPath = getBundleFilePath(project, variant).absolutePath

        return if (useGuard) {
            bundleTaskOutputPath.replace(".aab", "_$REINFORCE_APPGUARD_SUFFIX_AAB")
        } else {
            bundleTaskOutputPath
        }
    }

    // find target aab file path
    private fun findTargetBundlePath(
        variant: BaseVariant,
        hasDexGuard: Boolean,
        needGuard: Boolean,
        hasAabResGuard: Boolean
    ): String {
        val targetDir = "${project.buildDir}/outputs/bundle/${variant.name}"
        val targetBundleSuffix = when {
            hasDexGuard -> {
                "*-$REINFORCE_DEXGUARD_SUFFIX_AAB"
            }
            needGuard -> {
                "*$REINFORCE_APPGUARD_SUFFIX_AAB"
            }
            hasAabResGuard -> {
                "*$AAB_RESGUARD_SUFFIX_AAB"
            }
            else -> {
                "*.aab"
            }
        }

        println("[Jenkins] bundle file path suffix = $targetBundleSuffix")
        return project.fileTree(targetDir)
            .apply {
                files.forEach {
                    println("[Jenkins] file path = ${it.absolutePath}")
                }
            }
            .matching {
                it.include(targetBundleSuffix)
            }.firstOrNull()?.absolutePath ?: ""
    }

    /**
     * dexguard的mapping位置，版本不同，路径有些不一样。目前发现：
     * 9.0.5及以下版本，mapping文件在：[${project.buildDir}/outputs/dexguard/mapping/apk/${variant.flavorName}/${variant.buildType.name}/mapping.txt]
     * 9.0.8及以上版本，mapping文件在：[${project.buildDir}/outputs/dexguard/mapping/apk/${variant.flavorName}/${variant.buildType.name}/{cpu abi(like armeabi-v7a)}/mapping.txt]
     *
     * 暂时这么处理，在指定文件夹下面寻找mapping文件。后续若变动频繁，考虑通过extension让业务方在gradle中配置该路径
     */
    private fun findTargetMapping(
        project: Project,
        hasDexGuard: Boolean,
        variant: ApplicationVariant
    ): String {
        var mappingFile: String = ""
        try {
            mappingFile = if (hasDexGuard) {
                //如果是dexguard，那么在此目录没找到就是没找到，不能用原始output mapping，会混淆视听
                val path =
                    "${project.buildDir}/outputs/dexguard/mapping/apk/${variant.flavorName}/${variant.buildType.name}"
                project.fileTree(path)
                    .matching { it.include("**/mapping.txt") }
                    .firstOrNull()
                    ?.absolutePath ?: ""
            } else {
                variant.mappingFileProvider.get().asPath
            }
        } catch (e: Exception) {
            //do nothing
        }
        return mappingFile
    }

    /**
     * APM插件生成的方法mapping文件路径
     */
    private fun getAPMMethodMappingPath(project: Project, variantName: String): String? {
        val apmPluginApplied = project.plugins.hasPlugin("com.inke.apm-plugin")
        return if (apmPluginApplied) {
            File(project.buildDir, "outputs/mapping/$variantName/methodMapping.txt").absolutePath
        } else {
            println("[Jenkins] APM plugin not applied, doesn't have apm method mapping file.")
            null
        }

    }

    /**
     * 获取马甲包防关联插件生成的mapping文件
     */
    private fun findTargetObscureMapping(
        project: Project,
        variantName: String,
        hasObscureGuard: Boolean,
        hasArcherGuard: Boolean
    ): String {
        return when {
            hasObscureGuard -> "${project.buildDir}/generated/obscure_plugin_cache/mapping/obscureMapping.txt"
            hasArcherGuard -> "${project.buildDir}/generated/Archer_plugin_cache/${variantName.ifBlank { "default" }}/mapping/obscureMapping.txt"
            else -> ""
        }
    }

    /**
     * 获取ResGuard插件生成的res mapping文件
     */
    private fun findTargetResGuardResMapping(
        outputFile: File,
        hasResGuard: Boolean
    ): String {
        val fileName = outputFile.name.substring(0, outputFile.name.length - 4)
        println("[JenkinsPlugin] findTargetResGuardResMapping fileName:$fileName  hasResGuard:$hasResGuard")
        return if (hasResGuard) {
            val mapping = File(
                outputFile.parent,
                "/AndResGuard_${fileName}/resource_mapping_${fileName}.txt"
            ).absolutePath
            println("[JenkinsPlugin] findTargetResGuardResMapping mapping:$mapping")
            mapping
        } else ""
    }

}